package thinkingInJava.concurrency;
// page 744 将 RestaurantWithQueues.java 增加一个桌子，每个桌子可以有多个人
//: concurrency/restaurant2/E36_RestaurantWithQueues2.java
//{Args: 5}
/******************** Exercise 36 ************************
 * Modify RestaurantWithQueues.java so there’s one
 * OrderTicket object per table. Change order to
 * orderTicket, and add a Table class, with multiple
 * Customers per table.
 *********************************************************/
import java.util.concurrent.*;

import thinkingInJava.enumerated.menu.Course;
import thinkingInJava.enumerated.menu.Food;

import java.util.*;
import static thinkingInJava.YTool.YPrint.*;
//This is consisted of many orders, and there's one ticket
//per table:
class OrderTicket {
	private static int counter;
	private final int id = counter++;
	private final Table table;
	private final List<Order2> orders =
			Collections.synchronizedList(new LinkedList<Order2>());
	public OrderTicket(Table table) { this.table = table; }
	public WaitPerson4 getWaitPerson() {
		return table.getWaitPerson();
	}
	public void placeOrder(Customer3 cust, Food food) {
		Order2 order = new Order2(cust, food);
		orders.add(order);
		order.setOrderTicket(this);
	}
	public List<Order2> getOrders() { return orders; }
	public String toString() {
		StringBuilder sb = new StringBuilder(
				"Order2 Ticket: " + id + " for: " + table + "\n");
		synchronized(orders) {
			for(Order2 order : orders)
				sb.append(order.toString() + "\n");
		}
		//Prune away the last added 'new-line' character
		return sb.substring(0, sb.length() - 1).toString();
	}
}
class Table implements Runnable {
	private static int counter;
	private final int id = counter++;
	private final WaitPerson4 waitPerson;
	private final List<Customer3> customers;
	private final OrderTicket orderTicket =
			new OrderTicket(this);
	private final CyclicBarrier barrier;
	private final int nCustomers;
	private final ExecutorService exec;
	public Table(WaitPerson4 waitPerson, int nCustomers,
			ExecutorService e) {
		this.waitPerson = waitPerson;
		customers = Collections.synchronizedList(
				new LinkedList<Customer3>());
		this.nCustomers = nCustomers;
		exec = e;
		barrier = new CyclicBarrier(nCustomers + 1,
				new Runnable() {
			public void run() {
				print(orderTicket.toString());
			}
		});
	}
	public WaitPerson4 getWaitPerson() { return waitPerson; }
	public void placeOrder(Customer3 cust, Food food) {
		orderTicket.placeOrder(cust, food);
	}
	public void run() {
		Customer3 customer;
		for(int i = 0; i < nCustomers; i++) {
			customers.add(customer = new Customer3(this, barrier));
			exec.execute(customer);
		}
		try {
			barrier.await();
		} catch(InterruptedException ie) {
			print(this + " interrupted");
			return;
		} catch(BrokenBarrierException e) {
			throw new RuntimeException(e);
		}
		waitPerson.placeOrderTicket(orderTicket);
	}
	public String toString() {
		StringBuilder sb = new StringBuilder(
				"Table: " + id + " served by: " + waitPerson + "\n");
		synchronized(customers) {
			for(Customer3 customer : customers)
				sb.append(customer.toString() + "\n");
		}
		return sb.substring(0, sb.length() - 1).toString();
	}
}
//This is part of an order ticket (given to the chef):
class Order2 {
	private static int counter;
	private final int id;
	private volatile OrderTicket orderTicket;
	private final Customer3 customer;
	private final Food food;
	public Order2(Customer3 cust, Food f) {
		customer = cust;
		food = f;
		synchronized(Order2.class) { id = counter++; }
	}
	void setOrderTicket(OrderTicket orderTicket) {
		this.orderTicket = orderTicket;
	}
	public OrderTicket getOrderTicket() {
		return orderTicket;
	}
	public Food item() { return food; }
	public Customer3 getCustomer() { return customer; }
	public String toString() {
		return "Order2: " + id + " item: " + food +
				" for: " + customer;
	}
}
//This is what comes back from the chef:
class Plate2 {
	private final Order2 order;
	private final Food food;
	public Plate2(Order2 ord, Food f) {
		order = ord;
		food = f;
	}
	public Order2 getOrder() { return order; }
	public Food getFood() { return food; }
	public String toString() { return food.toString(); }
}
class Customer3 implements Runnable {
	private static int counter;
	private final int id;
	private final CyclicBarrier barrier;
	private final Table table;
	private int nPlates; // Number of plates ordered
	public Customer3(Table table, CyclicBarrier barrier) {
		this.table = table;
		this.barrier = barrier;
		synchronized(Customer3.class) { id = counter++; }
	}
	//Only one course at a time can be received:
	private final SynchronousQueue<Plate2> placeSetting =
			new SynchronousQueue<Plate2>();
	public void
	deliver(Plate2 p) throws InterruptedException {
		//Only blocks if customer is still
		//eating the previous course:
		placeSetting.put(p);
	}
	public void run() {
		//First place an order:
		for(Course course : Course.values()) {
			Food food = course.randomSelection();
			table.placeOrder(this, food);
			++nPlates;
		}
		try {
			barrier.await();
		} catch(InterruptedException ie) {
			print(this + " interrupted while ordering meal");
			return;
		} catch(BrokenBarrierException e) {
			throw new RuntimeException(e);
		}
		//Now wait for each ordered plate:
		for(int i = 0; i < nPlates; i++)
			try {
				//Blocks until course has been delivered:
				print(this + "eating " + placeSetting.take());
			} catch(InterruptedException e) {
				print(this + "waiting for meal interrupted");
				return;
			}
		print(this + "finished meal, leaving");
	}
	public String toString() {
		return "Customer3 " + id + " ";
	}
}
class WaitPerson4 implements Runnable {
	private static int counter;
	private final int id = counter++;
	private final Restaurant5 restaurant;
	final BlockingQueue<Plate2> filledOrders =
			new LinkedBlockingQueue<Plate2>();
	public WaitPerson4(Restaurant5 rest) { restaurant = rest; }
	public void placeOrderTicket(OrderTicket orderTicket) {
		try {
			//Shouldn't actually block because this is
			//a LinkedBlockingQueue with no size limit:
			restaurant.orderTickets.put(orderTicket);
		} catch(InterruptedException e) {
			print(this + " placeOrderTicket interrupted");
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				//Blocks until a course is ready
				Plate2 plate = filledOrders.take();
				print(this + "received " + plate +
						" delivering to " +
						plate.getOrder().getCustomer());
				plate.getOrder().getCustomer().deliver(plate);
			}
		} catch(InterruptedException e) {
			print(this + " interrupted");
		}
		print(this + " off duty");
	}
	public String toString() {
		return "WaitPerson4 " + id + " ";
	}
}
class Chef4 implements Runnable {
	private static int counter;
	private final int id = counter++;
	private final Restaurant5 restaurant;
	private static Random rand = new Random(47);
	public Chef4(Restaurant5 rest) { restaurant = rest; }
	public void run() {
		try {
			while(!Thread.interrupted()) {
				//Blocks until an order ticket appears:
				OrderTicket orderTicket =
						restaurant.orderTickets.take();
				List<Order2> orders = orderTicket.getOrders();
				synchronized(orders) {
					for(Order2 order : orders) {
						Food requestedItem = order.item();
						//Time to prepare order:
						TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
						Plate2 plate = new Plate2(order, requestedItem);
						order.getOrderTicket().getWaitPerson().
						filledOrders.put(plate);
					}
				}
			}
		} catch(InterruptedException e) {
			print(this + " interrupted");
		}
		print(this + " off duty");
	}
	public String toString() { return "Chef4 " + id + " "; }
}
class Restaurant5 implements Runnable {
	private List<WaitPerson4> waitPersons =
			new ArrayList<WaitPerson4>();
	private List<Chef4> chefs = new ArrayList<Chef4>();
	private ExecutorService exec;
	private static Random rand = new Random(47);
	final BlockingQueue<OrderTicket> orderTickets =
			new LinkedBlockingQueue<OrderTicket>();
	public Restaurant5(ExecutorService e, int nWaitPersons,
			int nChefs) {
		exec = e;
		for(int i = 0; i < nWaitPersons; i++) {
			WaitPerson4 waitPerson = new WaitPerson4(this);
			waitPersons.add(waitPerson);
			exec.execute(waitPerson);
		}
		for(int i = 0; i < nChefs; i++) {
			Chef4 chef = new Chef4(this);
			chefs.add(chef);
			exec.execute(chef);
		}
	}
	public void run() {
		try {
			while(!Thread.interrupted()) {
				//A new group of customers arrive; assign a
				//WaitPerson4:
				WaitPerson4 wp = waitPersons.get(
						rand.nextInt(waitPersons.size()));
				int nCustomers = rand.nextInt(4) + 1;
				Table t = new Table(wp, nCustomers, exec);
				exec.execute(t);
				TimeUnit.MILLISECONDS.sleep(400 * nCustomers);
			}
		} catch(InterruptedException e) {
			print("Restaurant5 interrupted");
		}
		print("Restaurant5 closing");
	}
}
public class E36_RestaurantWithQueues2 {
	public static void main(String[] args) throws Exception {
		ExecutorService exec = Executors.newCachedThreadPool();
		Restaurant5 restaurant = new Restaurant5(exec, 5, 2);
		exec.execute(restaurant);
		if(args.length > 0) // Optional argument
			TimeUnit.SECONDS.sleep(new Integer(args[0]));
		else {
			print("Press 'ENTER' to quit");
			System.in.read();
		}
		exec.shutdownNow();
	}
} /* Output: (Sample)
Concurrency  675
Order2 Ticket: 0 for: Table: 0 served by: WaitPerson4 3
Customer3 0
Customer3 1
Order2: 0 item: SPRING_ROLLS for: Customer3 1
Order2: 1 item: SPRING_ROLLS for: Customer3 0
Order2: 2 item: VINDALOO for: Customer3 0
Order2: 3 item: BURRITO for: Customer3 1
Order2: 4 item: GELATO for: Customer3 0
Order2: 5 item: CREME_CARAMEL for: Customer3 1
Order2: 6 item: BLACK_COFFEE for: Customer3 0
Order2: 7 item: TEA for: Customer3 1
WaitPerson4 3 received SPRING_ROLLS delivering to Customer3 1
Customer3 1 eating SPRING_ROLLS
WaitPerson4 3 received SPRING_ROLLS delivering to Customer3 0
Customer3 0 eating SPRING_ROLLS
WaitPerson4 3 received VINDALOO delivering to Customer3 0
Customer3 0 eating VINDALOO
Order2 Ticket: 1 for: Table: 1 served by: WaitPerson4 3
Customer3 2
Order2: 8 item: SALAD for: Customer3 2
Order2: 9 item: BURRITO for: Customer3 2
Order2: 10 item: FRUIT for: Customer3 2
Order2: 11 item: TEA for: Customer3 2
WaitPerson4 3 received BURRITO delivering to Customer3 1
Customer3 1 eating BURRITO
...
Customer3 12 waiting for meal interrupted
WaitPerson4 3 interrupted
Customer3 9 waiting for meal interrupted
Customer3 13 waiting for meal interrupted
WaitPerson4 3 off duty
WaitPerson4 1 interrupted
Customer3 8 waiting for meal interrupted
WaitPerson4 2 interrupted
Customer3 7 waiting for meal interrupted
WaitPerson4 0 interrupted
Restaurant5 interrupted
WaitPerson4 1 off duty
WaitPerson4 2 off duty
Chef4 0 interrupted
Customer3 14 waiting for meal interrupted
Customer3 10 waiting for meal interrupted
WaitPerson4 0 off duty
WaitPerson4 4 interrupted
Restaurant5 closing
Chef4 0 off duty
WaitPerson4 4 off duty
Thinking in Java, 4 th Edition Annotated Solution Guide  676
Chef4 1 interrupted
Customer3 11 waiting for meal interrupted
Chef4 1 off duty
 *///:~
//The exercise is vague, allowing many approaches. Our solution works well, but
//try experimenting with others. For example, to increase restaurant productivity,
//eliminate the inefficiency of repeated roundtrips to deliver each plate as soon as
//possible, perhaps grouping food types for delivery.
//The Table class creates a group order. Each customer at a table orders, and
//when they are all done, Table passes the order ticket to a waitperson. A
//CyclicBarrier synchronizes the tasks.
//The OrderTicket consists of many orders, and each chef handles one ticket.
//Restaurant5 produces Table tasks, so customers come in groups and occupy a
//particular table.
//The rest of the program follows the original version.
